using System;
using System.Collections.Generic;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Runtime.InteropServices;
using Baik.Interface;
using Baik.Interface.Execution;
using Baik.Interface.Expression;
using Baik.Interface.Framework;
using Baik.Interface.Token;

namespace Baik.Expression
{
    public class ExpressionParser : IExpressionParser
    {
        public ExpressionParser(IInjector inject, ITokenizer tokenizer)
        {
            Tokenizer = tokenizer;
            Expressions = new Stack<IExpression>();
            Inject = inject;
        }

        public void Clear()
        {
            Expressions.Clear();
        }
        public ITokenizer Tokenizer { get; private set; }
        public Stack<IExpression> Expressions { get; private set; }
        public IInjector Inject { get; private set; }
        public IEnumerable<IExpression> ParseCurlyBlock(IQue<IToken> tokens)
        {
            var lst = new List<IExpression>();
            while (!tokens.Empty && tokens.Peek().Type != TokenType.CloseCurly)
            {
                lst.Add(Parse(tokens));
            }
            return lst;
        }
        public IEnumerable<IExpression> ParseParenBlock(IQue<IToken> tokens)
        {
            var lst = new List<IExpression>();
            var peek = tokens.Peek();
            while (!tokens.Empty && peek.Type != TokenType.CloseParen)
            {
                var rtn = Parse(tokens);
                if(rtn != null) lst.Add(rtn);
                peek = tokens.Peek();
                while (peek.Type == TokenType.Comma)
                {
                    tokens.Pop();
                    peek = tokens.Peek();
                }
            }
            return lst;
        }
        public IEnumerable<IExpression> ParseSquareBlock(IQue<IToken> tokens)
        {
            var lst = new List<IExpression>();
            var peek = tokens.Peek();
            while (!tokens.Empty && peek.Type != TokenType.CloseSquare)
            {
                var rtn = Parse(tokens);
                if (rtn != null) lst.Add(rtn);
                peek = tokens.Peek();
                while (peek.Type == TokenType.Comma)
                {
                    tokens.Pop();
                    peek = tokens.Peek();
                }
            }
            return lst;
        }

        public IExpression Parse(string code)
        {
            return Parse(Tokenizer.Tokenise(code));
        }
        public IExpression Parse(IQue<IToken> tokens)
        {
            IExpression rtn = null;
            var EndOfExpression = false;
            while(!(EndOfExpression || tokens.Empty))
            {
                while (tokens.Peek().Type == TokenType.WhiteSpace) tokens.Pop();
                switch (tokens.Peek().Type)
                {
                    case TokenType.OpenCurly:
                        rtn = ParseObject(tokens);
                        break;
                    case TokenType.Colon:
                        rtn = ParseAssignment(tokens);
                        EndOfExpression = true;
                        Expressions.Clear();
                        break;
                    case TokenType.Ident:
                        rtn = ParseReference(tokens);
                        break;
                    case TokenType.DotToken:
                        rtn = ParseSubReference(tokens);
                        break;
                    case TokenType.OpenParen:
                        if (Expressions.Count > 0  && IsCallableOrIndexable(Expressions.Peek()))
                        {
                            rtn = ParseCall(tokens);
                        }
                        else
                        {
                            rtn = ParseFunction(tokens);
                        }
                        break;
                    case TokenType.OpenSquare:
                        if (Expressions.Count() > 0 && IsCallableOrIndexable(Expressions.Peek()))
                        {   
                            rtn = ParseArrayReference(tokens);
                        }
                        else
                        {
                            rtn = ParseArray(tokens);
                        }
                        break;
                    case TokenType.Return:
                        rtn = ParseReturn(tokens);
                        EndOfExpression = true;
                        Expressions.Clear();
                        break;
                    case TokenType.Undefined:
                        rtn = ParseUnknownLiteral(tokens);
                        break;
                    case TokenType.True:
                        rtn = ParseTrueLiteral(tokens);
                        break;
                    case TokenType.False:
                        rtn = ParseFalseLiteral(tokens);
                        break;
                    case TokenType.String:
                        rtn = ParseStringLiteral(tokens);
                        break;
                    case TokenType.Number:
                        rtn = ParseNumberLiteral(tokens);
                        break;
                    case TokenType.Comma:
                    case TokenType.Semicolon:
                        tokens.Pop();
                        Expressions.Clear();
                        EndOfExpression = true;
                        break;
                    case TokenType.CloseCurly:
                    case TokenType.CloseParen:
                    case TokenType.CloseSquare:
                        EndOfExpression = true;
                        break;
                default:
                    tokens.Pop();
                    break;
            }


            if (rtn == null) return null;
           if(!EndOfExpression) Expressions.Push(rtn);
        }
        return rtn;
    }
        private IExpression ParseArray(IQue<IToken> tokens)
        {
            var rtn = Inject.Instantiate<IArrayLiteral>();
            tokens.Pop();
            var parts = ParseSquareBlock(tokens);
            tokens.Pop();
            rtn.Expressions = parts;
            return rtn;
        }

        private bool IsCallableOrIndexable(IExpression exp)
        {
            return exp is IReference || exp is IFunctionLiteral || exp is ICall;
        }
        private IExpression ParseArrayReference(IQue<IToken> tokens)
        {
            var rtn = Inject.Instantiate<IArrayReference>();
            var left = Expressions.Pop();
            tokens.Pop();
            var val = Parse(tokens);
            tokens.Pop();
            rtn = Inject.Instantiate<IArrayReference>();
            rtn.Reference = left;
            rtn.Index = val;
            return rtn;
        }
        public IExpression ParseObject(IQue<IToken> tokens)
        {
            
            tokens.Pop();
            var rtn = Inject.Instantiate<IObjectLiteral>();
            var exps = ParseCurlyBlock(tokens);
            tokens.Pop();
            rtn.Assigments = exps.OfType<IAssigment>();
            
            return rtn;
        }
        public IExpression ParseFunction(IQue<IToken> tokens)
        {
            tokens.Pop();
            var rtn = Inject.Instantiate<IFunctionLiteral>();
            var parms = ParseParenBlock(tokens);
            tokens.Pop();
            tokens.Pop();
            var exp = ParseCurlyBlock(tokens);
            tokens.Pop();
            rtn.Params = parms;
            rtn.Expressions = exp;
            return rtn;
        }
        public IExpression ParseAssignment(IQue<IToken> tokens)
        {
            var left = Expressions.Pop();
            tokens.Pop();
            var right = Parse(tokens);
            var rtn = Inject.Instantiate<IAssigment>();
            rtn.Reference = left;
            rtn.Value = right;
            return rtn;
        }
        public IExpression ParseReference(IQue<IToken> tokens)
        {
            var token = tokens.Pop();

            string name = token.Value;
            var rtn = Inject.Instantiate<IReference>();
            rtn.Name = name;
            rtn.Host = null;
            return rtn;
        }
        public IExpression ParseSubReference(IQue<IToken> tokens)
        {
            tokens.Pop();
            var token = tokens.Pop();
            string name = token.Value;
            var rtn = Inject.Instantiate<IReference>();
            rtn.Name = name;
            rtn.Host = Expressions.Pop();
            return rtn;
        }
        public IExpression ParseCall(IQue<IToken> tokens)
        {
            tokens.Pop();
            var xp = new Stack<IExpression>();
            while(Expressions.Any())xp.Push(Expressions.Pop());
            var parms = ParseParenBlock(tokens);
            tokens.Pop();
            while(xp.Any())Expressions.Push(xp.Pop());
            var host = Expressions.Pop();
            var rtn = Inject.Instantiate<ICall>();
            rtn.Reference = host;
            rtn.Params = parms;
            return rtn;
        }

        public IExpression ParseStringLiteral(IQue<IToken> tokens)
        {
            var token = tokens.Pop();
            var rtn = Inject.Instantiate<IStringLiteral>();
            rtn.Value = token.Value;
            return rtn;
        }
        public IExpression ParseNumberLiteral(IQue<IToken> tokens)
        {
            var token = tokens.Pop();
            var rtn = Inject.Instantiate<INumberLiteral>();
            rtn.Value = Convert.ToDouble(token.Value);
            return rtn;
        }
        public IExpression ParseTrueLiteral(IQue<IToken> tokens)
        {
            var token = tokens.Pop();
            var rtn = Inject.Instantiate<Baik.Interface.Expression.ITrue>();
            return rtn;
        }
        public IExpression ParseFalseLiteral(IQue<IToken> tokens)
        {
            var token = tokens.Pop();
            var rtn = Inject.Instantiate<Baik.Interface.Expression.IFalse>();
            return rtn;
        }
        public IExpression ParseUnknownLiteral(IQue<IToken> tokens)
        {
            var token = tokens.Pop();
            var rtn = Inject.Instantiate<Baik.Interface.Expression.IUndefined>();
            return rtn;
        }

        public IExpression ParseReturn(IQue<IToken> tokens)
        {
            var token = tokens.Pop();
            var rtn = Inject.Instantiate<Baik.Interface.Expression.IReturn>();
            var right = Parse(tokens);
            
            rtn.Value = right;
            return rtn;
        }
    }
}

